using System.Diagnostics;
using System.Collections.Generic;

//
// * Copyright (c) 2002-2009 "Neo Technology,"
// *     Network Engine for Objects in Lund AB [http://neotechnology.com]
// *
// * This file is part of Neo4j.
// * 
// * Neo4j is free software: you can redistribute it and/or modify
// * it under the terms of the GNU Affero General Public License as
// * published by the Free Software Foundation, either version 3 of the
// * License, or (at your option) any later version.
// * 
// * This program is distributed in the hope that it will be useful,
// * but WITHOUT ANY WARRANTY; without even the implied warranty of
// * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// * GNU Affero General Public License for more details.
// * 
// * You should have received a copy of the GNU Affero General Public License
// * along with this program. If not, see <http://www.gnu.org/licenses/>.
// 
namespace org.neo4j.kernel.impl.nioneo.store
{


	using ReadOnlyNeoException = org.neo4j.kernel.impl.core.ReadOnlyNeoException;

///
/// <summary> * An abstract representation of a store. A store is a file that contains
/// * records. Each record has a fixed size (<CODE>getRecordSize()</CODE>) so
/// * the position for a record can be calculated by 
/// * <CODE>id * getRecordSize()</CODE>.
/// * <p>
/// * A store has an <seealso cref="IdGenerator"/> managing the records that are free or in
/// * use. </summary>
/// 
	public abstract class AbstractStore : CommonAbstractStore
	{
///    
///     <summary> * Returnes the fixed size of each record in this store.
///     *  </summary>
///     * <returns> The record size </returns>
///     
		public abstract int getRecordSize();

///    
///     <summary> * Creates a new empty store. The factory method returning an implementation
///     * of some store type should make use of this method to initialize an empty
///     * store.
///     * <p>
///     * This method will create a empty store containing the descriptor returned
///     * by the <CODE>getTypeAndVersionDescriptor()</CODE>. The id generator
///     * used by this store will also be created
///     *  </summary>
///     * <param name="fileName">
///     *            The file name of the store that will be created </param>
///     * <param name="typeAndVersionDescriptor">
///     *            The type and version descriptor that identifies this store </param>
///     * <exception cref="IOException">
///     *             If fileName is null or if file exists </exception>
///     
		protected internal static void createEmptyStore(string fileName, string typeAndVersionDescriptor)
		{
		// sanity checks
			if (fileName == null)
			{
				throw new IllegalArgumentException("Null filename");
			}
			File file = new File(fileName);
			if (file.exists())
			{
				throw new IllegalStateException("Can't create store[" + fileName + "], file already exists");
			}

		// write the header
			try
			{
				FileChannel channel = new FileOutputStream(fileName).getChannel();
				int endHeaderSize = typeAndVersionDescriptor.getBytes().length;
				ByteBuffer buffer = ByteBuffer.allocate(endHeaderSize);
				buffer.put(typeAndVersionDescriptor.getBytes()).flip();
				channel.write(buffer);
				channel.force(false);
				channel.Close();
			}
			catch (IOException e)
			{
				throw new UnderlyingStorageException("Unable to create store " + fileName, e);
			}
			IdGeneratorImpl.createGenerator(fileName + ".id");
		}

		public AbstractStore<T1>(string fileName, Map<T1> config) : base(fileName, config)
		{
		}

		public AbstractStore(string fileName) : base(fileName)
		{
		}

		protected internal override void loadStorage()
		{
			try
			{
				long fileSize = getFileChannel().size();
				string expectedVersion = getTypeAndVersionDescriptor();
				sbyte[] version = new sbyte[expectedVersion.getBytes().length];
				ByteBuffer buffer = ByteBuffer.wrap(version);
				if (fileSize >= version.Length)
				{
					getFileChannel().position(fileSize - version.Length);
				}
				else if (!isReadOnly())
				{
					setStoreNotOk();
				}
				getFileChannel().read(buffer);
				if (!expectedVersion.Equals(new string(version)) && !isReadOnly())
				{
					if (!versionFound(new string(version)))
					{
						setStoreNotOk();
					}
				}
				if (getRecordSize() != 0 && (fileSize - version.Length) % getRecordSize() != 0 && !isReadOnly())
				{
					setStoreNotOk();
				}
				if (getStoreOk() && !isReadOnly())
				{
					getFileChannel().truncate(fileSize - version.Length);
				}
			}
			catch (IOException e)
			{
				throw new UnderlyingStorageException("Unable to load store " + getStorageFileName(), e);
			}
			try
			{
				if (!isReadOnly())
				{
					openIdGenerator();
				}
				else
				{
					openReadOnlyIdGenerator(getRecordSize());
				}
			}
			catch (InvalidIdGeneratorException e)
			{
				setStoreNotOk();
			}
			setWindowPool(new PersistenceWindowPool(getStorageFileName(), getRecordSize(), getFileChannel(), getMappedMem(), getIfMemoryMapped(), isReadOnly()));
		}

///    
///     <summary> * Returns the highest id in use by this store.
///     *  </summary>
///     * <returns> The highest id in use </returns>
///     
		public override long getHighId()
		{
			return base.getHighId();
		}

///    
///     <summary> * Sets the high id of <seealso cref="IdGenerator"/>.
///     *  </summary>
///     * <param name="id">
///     *            The high id </param>
///     
		public override void setHighId(int id)
		{
			base.setHighId(id);
		}

		protected internal virtual void updateHighId()
		{
			try
			{
				long highId = getFileChannel().size() / getRecordSize();
				setHighId(highId);
			}
			catch (IOException e)
			{
				throw new UnderlyingStorageException(e);
			}
		}

//JAVA TO VB & C# CONVERTER WARNING: Method 'throws' clauses are not available in .NET:
//ORIGINAL LINE: private int findHighIdBackwards() throws IOException
		private int findHighIdBackwards()
		{
			FileChannel fileChannel = getFileChannel();
			int recordSize = getRecordSize();
			long fileSize = fileChannel.size();
			long highId = fileSize / recordSize;
			ByteBuffer byteBuffer = ByteBuffer.allocate(1);
			for (long i = highId; i > 0; i--)
			{
				fileChannel.position(i * recordSize);
				if (fileChannel.read(byteBuffer) > 0)
				{
					byteBuffer.flip();
					sbyte inUse = byteBuffer.get();
					byteBuffer.Clear();
					if (inUse != 0)
					{
						return (int) i;
					}
				}
			}
			return 0;
		}

///    
///     <summary> * Rebuilds the <seealso cref="IdGenerator"/> by looping through all records and
///     * checking if record in use or not.
///     *  </summary>
///     * <exception cref="IOException">
///     *             if unable to rebuild the id generator </exception>
///     
		protected internal override void rebuildIdGenerator()
		{
			if (isReadOnly())
			{
				throw new ReadOnlyNeoException();
			}
		// TODO: fix this hardcoding
			const sbyte RECORD_NOT_IN_USE = 0;

			logger.fine("Rebuilding id generator for[" + getStorageFileName() + "] ...");
			closeIdGenerator();
			File file = new File(getStorageFileName() + ".id");
			if (file.exists())
			{
				bool success = file.delete();
				Debug.Assert(success);
			}
			IdGeneratorImpl.createGenerator(getStorageFileName() + ".id");
			openIdGenerator();
			FileChannel fileChannel = getFileChannel();
			long highId = 1;
			long defraggedCount = 0;
			try
			{
				long fileSize = fileChannel.size();
				int recordSize = getRecordSize();
				bool fullRebuild = true;
				if (getConfig() != null)
				{
					string mode = (string) getConfig().get("rebuild_idgenerators_fast");
					if (mode != null && mode.ToLower().Equals("true"))
					{
						fullRebuild = false;
						highId = findHighIdBackwards();
					}
				}
				ByteBuffer byteBuffer = ByteBuffer.wrap(new sbyte[1]);
				LinkedList<int> freeIdList = new LinkedList<int>();
				if (fullRebuild)
				{
					for (long i = 0; i * recordSize < fileSize && recordSize > 0; i++)
					{
						fileChannel.position(i * recordSize);
						fileChannel.read(byteBuffer);
						byteBuffer.flip();
						sbyte inUse = byteBuffer.get();
						byteBuffer.flip();
						nextId();
						if (inUse == RECORD_NOT_IN_USE)
						{
							freeIdList.AddLast((int) i);
						}
						else
						{
							highId = i;
							while (!freeIdList.Count == 0)
							{
								freeId(freeIdList.RemoveFirst());
								defraggedCount++;
							}
						}
					}
				}
			}
			catch (IOException e)
			{
				throw new UnderlyingStorageException("Unable to rebuild id generator " + getStorageFileName(), e);
			}
			setHighId(highId + 1);
			logger.fine("[" + getStorageFileName() + "] high id=" + getHighId() + " (defragged=" + defraggedCount + ")");
			closeIdGenerator();
			openIdGenerator();
		}
	}
}